 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import java.util.BitSet ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.Map ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IPropertyListener;
 import org.eclipse.ui.ISaveablePart;
 import org.eclipse.ui.ISaveablesLifecycleListener;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.IWorkbenchPart2;
 import org.eclipse.ui.IWorkbenchPart3;
 import org.eclipse.ui.IWorkbenchPartConstants;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.IWorkbenchPartSite;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.misc.UIListenerLogging;
 import org.eclipse.ui.internal.util.Util;

 /**
  *
  */
 public abstract class WorkbenchPartReference implements IWorkbenchPartReference {

     /**
      * Internal property ID: Indicates that the underlying part was created
      */
     public static final int INTERNAL_PROPERTY_OPENED = 0x211;
     
     /**
      * Internal property ID: Indicates that the underlying part was destroyed
      */
     public static final int INTERNAL_PROPERTY_CLOSED = 0x212;

     /**
      * Internal property ID: Indicates that the result of IEditorReference.isPinned()
      */
     public static final int INTERNAL_PROPERTY_PINNED = 0x213;

     /**
      * Internal property ID: Indicates that the result of getVisible() has changed
      */
     public static final int INTERNAL_PROPERTY_VISIBLE = 0x214;

     /**
      * Internal property ID: Indicates that the result of isZoomed() has changed
      */
     public static final int INTERNAL_PROPERTY_ZOOMED = 0x215;

     /**
      * Internal property ID: Indicates that the part has an active child and the
      * active child has changed. (fired by PartStack)
      */
     public static final int INTERNAL_PROPERTY_ACTIVE_CHILD_CHANGED = 0x216;

     /**
      * Internal property ID: Indicates that changed in the min / max
      * state has changed
      */
     public static final int INTERNAL_PROPERTY_MAXIMIZED = 0x217;

     // State constants //////////////////////////////

     /**
      * State constant indicating that the part is not created yet
      */
     public static int STATE_LAZY = 0;
      
     /**
      * State constant indicating that the part is in the process of being created
      */
     public static int STATE_CREATION_IN_PROGRESS = 1;
     
     /**
      * State constant indicating that the part has been created
      */
     public static int STATE_CREATED = 2;
     
     /**
      * State constant indicating that the reference has been disposed (the reference shouldn't be
      * used anymore)
      */
     public static int STATE_DISPOSED = 3;
   
     /**
      * Current state of the reference. Used to detect recursive creation errors, disposed
      * references, etc.
      */
     private int state = STATE_LAZY;
    
     protected IWorkbenchPart part;

     private String id;

     protected PartPane pane;

     private boolean pinned = false;
     
     private String title;

     private String tooltip;

     /**
      * Stores the current Image for this part reference. Lazily created. Null if not allocated.
      */
     private Image image = null;
     
     private ImageDescriptor defaultImageDescriptor;
     
     /**
      * Stores the current image descriptor for the part.
      */
     private ImageDescriptor imageDescriptor;

     /**
      * API listener list
      */
     private ListenerList propChangeListeners = new ListenerList();

     /**
      * Internal listener list. Listens to the INTERNAL_PROPERTY_* property change events that are not yet API.
      * TODO: Make these properties API in 3.2
      */
     private ListenerList internalPropChangeListeners = new ListenerList();
     
     private ListenerList partChangeListeners = new ListenerList();
     
     private String partName;

     private String contentDescription;
     
     protected Map propertyCache = new HashMap ();
     
     /**
      * Used to remember which events have been queued.
      */
     private BitSet queuedEvents = new BitSet ();

     private boolean queueEvents = false;

     private static DisposeListener prematureDisposeListener = new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
             WorkbenchPlugin.log(new RuntimeException ("Widget disposed too early!")); //$NON-NLS-1$
 }
     };
     
     private IPropertyListener propertyChangeListener = new IPropertyListener() {
         /* (non-Javadoc)
          * @see org.eclipse.ui.IPropertyListener#propertyChanged(java.lang.Object, int)
          */
         public void propertyChanged(Object source, int propId) {
             partPropertyChanged(source, propId);
         }
     };
     
     private IPropertyChangeListener partPropertyChangeListener = new IPropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent event) {
             partPropertyChanged(event);
         }
     };
     
     public WorkbenchPartReference() {
         //no-op
 }
     
     public boolean isDisposed() {
         return state == STATE_DISPOSED;
     }
     
     protected void checkReference() {
         if (state == STATE_DISPOSED) {
             throw new RuntimeException ("Error: IWorkbenchPartReference disposed"); //$NON-NLS-1$
 }
     }
     
     /**
      * Calling this with deferEvents(true) will queue all property change events until a subsequent
      * call to deferEvents(false). This should be used at the beginning of a batch of related changes
      * to prevent duplicate property change events from being sent.
      *
      * @param shouldQueue
      */
     private void deferEvents(boolean shouldQueue) {
         queueEvents = shouldQueue;

         if (queueEvents == false) {
             // do not use nextSetBit, to allow compilation against JCL Foundation (bug 80053)
 for (int i = 0, n = queuedEvents.size(); i < n; ++i) {
                 if (queuedEvents.get(i)) {
                     firePropertyChange(i);
                     queuedEvents.clear(i);
                 }
             }
         }
     }

     protected void setTitle(String newTitle) {
         if (Util.equals(title, newTitle)) {
             return;
         }

         title = newTitle;
         firePropertyChange(IWorkbenchPartConstants.PROP_TITLE);
     }

     protected void setPartName(String newPartName) {
         if (Util.equals(partName, newPartName)) {
             return;
         }

         partName = newPartName;
         firePropertyChange(IWorkbenchPartConstants.PROP_PART_NAME);
     }

     protected void setContentDescription(String newContentDescription) {
         if (Util.equals(contentDescription, newContentDescription)) {
             return;
         }

         contentDescription = newContentDescription;
         firePropertyChange(IWorkbenchPartConstants.PROP_CONTENT_DESCRIPTION);
     }

     protected void setImageDescriptor(ImageDescriptor descriptor) {
         if (Util.equals(imageDescriptor, descriptor)) {
             return;
         }

         Image oldImage = image;
         ImageDescriptor oldDescriptor = imageDescriptor;
         image = null;
         imageDescriptor = descriptor;
         
         // Don't queue events triggered by image changes. We'll dispose the image
 // immediately after firing the event, so we need to fire it right away.
 immediateFirePropertyChange(IWorkbenchPartConstants.PROP_TITLE);
         if (queueEvents) {
             // If there's a PROP_TITLE event queued, remove it from the queue because
 // we've just fired it.
 queuedEvents.clear(IWorkbenchPartConstants.PROP_TITLE);
         }
         
         // If we had allocated the old image, deallocate it now (AFTER we fire the property change
 // -- listeners may need to clean up references to the old image)
 if (oldImage != null) {
             JFaceResources.getResources().destroy(oldDescriptor);
         }
     }
     
     protected void setToolTip(String newToolTip) {
         if (Util.equals(tooltip, newToolTip)) {
             return;
         }

         tooltip = newToolTip;
         firePropertyChange(IWorkbenchPartConstants.PROP_TITLE);
     }

     protected void partPropertyChanged(Object source, int propId) {

         // We handle these properties directly (some of them may be transformed
 // before firing events to workbench listeners)
 if (propId == IWorkbenchPartConstants.PROP_CONTENT_DESCRIPTION
                 || propId == IWorkbenchPartConstants.PROP_PART_NAME
                 || propId == IWorkbenchPartConstants.PROP_TITLE) {

             refreshFromPart();
         } else {
             // Any other properties are just reported to listeners verbatim
 firePropertyChange(propId);
         }
         
         // Let the model manager know as well
 if (propId == IWorkbenchPartConstants.PROP_DIRTY) {
             IWorkbenchPart actualPart = getPart(false);
             if (actualPart != null) {
                 SaveablesList modelManager = (SaveablesList) actualPart.getSite().getService(ISaveablesLifecycleListener.class);
                 modelManager.dirtyChanged(actualPart);
             }
         }
     }
     
     protected void partPropertyChanged(PropertyChangeEvent event) {
         firePartPropertyChange(event);
     }

     /**
      * Refreshes all cached values with the values from the real part
      */
     protected void refreshFromPart() {
         deferEvents(true);

         setPartName(computePartName());
         setTitle(computeTitle());
         setContentDescription(computeContentDescription());
         setToolTip(getRawToolTip());
         setImageDescriptor(computeImageDescriptor());

         deferEvents(false);
     }
     
     protected ImageDescriptor computeImageDescriptor() {
         if (part != null) {
             return ImageDescriptor.createFromImage(part.getTitleImage(), Display.getCurrent());
         }
         return defaultImageDescriptor;
     }

     public void init(String id, String title, String tooltip,
             ImageDescriptor desc, String paneName, String contentDescription) {
         Assert.isNotNull(id);
         Assert.isNotNull(title);
         Assert.isNotNull(tooltip);
         Assert.isNotNull(desc);
         Assert.isNotNull(paneName);
         Assert.isNotNull(contentDescription);
         
         this.id = id;
         this.title = title;
         this.tooltip = tooltip;
         this.partName = paneName;
         this.contentDescription = contentDescription;
         this.defaultImageDescriptor = desc;
         this.imageDescriptor = computeImageDescriptor();
     }

     /**
      * Releases any references maintained by this part reference
      * when its actual part becomes known (not called when it is disposed).
      */
     protected void releaseReferences() {

     }

     /* package */ void addInternalPropertyListener(IPropertyListener listener) {
         internalPropChangeListeners.add(listener);
     }
     
     /* package */ void removeInternalPropertyListener(IPropertyListener listener) {
         internalPropChangeListeners.remove(listener);
     }

     protected void fireInternalPropertyChange(int id) {
         Object listeners[] = internalPropChangeListeners.getListeners();
         for (int i = 0; i < listeners.length; i++) {
             ((IPropertyListener) listeners[i]).propertyChanged(this, id);
         }
     }
     
     /**
      * @see IWorkbenchPart
      */
     public void addPropertyListener(IPropertyListener listener) {
         // The properties of a disposed reference will never change, so don't
 // add listeners
 if (isDisposed()) {
             return;
         }
         
         propChangeListeners.add(listener);
     }

     /**
      * @see IWorkbenchPart
      */
     public void removePropertyListener(IPropertyListener listener) {
         // Currently I'm not calling checkReference here for fear of breaking things late in 3.1, but it may
 // make sense to do so later. For now we just turn it into a NOP if the reference is disposed.
 if (isDisposed()) {
             return;
         }
         propChangeListeners.remove(listener);
     }

     public final String getId() {
         if (part != null) {
             IWorkbenchPartSite site = part.getSite();
             if (site != null) {
                 return site.getId();
             }
         }
         return Util.safeString(id);
     }

     public String getTitleToolTip() {
         return Util.safeString(tooltip);
     }

     protected final String getRawToolTip() {
         return Util.safeString(part.getTitleToolTip());
     }

     /**
      * Returns the pane name for the part
      *
      * @return the pane name for the part
      */
     public String getPartName() {
         return Util.safeString(partName);
     }
     
     /**
      * Gets the part name directly from the associated workbench part,
      * or the empty string if none.
      *
      * @return
      */
     protected final String getRawPartName() {
         String result = ""; //$NON-NLS-1$

         if (part instanceof IWorkbenchPart2) {
             IWorkbenchPart2 part2 = (IWorkbenchPart2) part;

             result = Util.safeString(part2.getPartName());
         }

         return result;
     }

     protected String computePartName() {
         return getRawPartName();
     }

     /**
      * Returns the content description for this part.
      *
      * @return the pane name for the part
      */
     public String getContentDescription() {
         return Util.safeString(contentDescription);
     }

     /**
      * Computes a new content description for the part. Subclasses may override to change the
      * default behavior
      *
      * @return the new content description for the part
      */
     protected String computeContentDescription() {
         return getRawContentDescription();
     }

     /**
      * Returns the content description as set directly by the part, or the empty string if none
      *
      * @return the unmodified content description from the part (or the empty string if none)
      */
     protected final String getRawContentDescription() {
         if (part instanceof IWorkbenchPart2) {
             IWorkbenchPart2 part2 = (IWorkbenchPart2) part;

             return part2.getContentDescription();
         }

         return ""; //$NON-NLS-1$
 }

     public boolean isDirty() {
         if (!(part instanceof ISaveablePart)) {
             return false;
         }
         return ((ISaveablePart) part).isDirty();
     }

     public String getTitle() {
         return Util.safeString(title);
     }

     /**
      * Computes a new title for the part. Subclasses may override to change the default behavior.
      *
      * @return the title for the part
      */
     protected String computeTitle() {
         return getRawTitle();
     }

     /**
      * Returns the unmodified title for the part, or the empty string if none
      *
      * @return the unmodified title, as set by the IWorkbenchPart. Returns the empty string if none.
      */
     protected final String getRawTitle() {
         return Util.safeString(part.getTitle());
     }

     public final Image getTitleImage() {
         if (isDisposed()) {
             return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_DEF_VIEW);
         }
         
         if (image == null) {
             image = JFaceResources.getResources().createImageWithDefault(imageDescriptor);
         }
         return image;
     }
     
     public ImageDescriptor getTitleImageDescriptor() {
         if (isDisposed()) {
             return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_DEF_VIEW);
         }
         
         return imageDescriptor;
     }
     
     /* package */ void fireVisibilityChange() {
         fireInternalPropertyChange(INTERNAL_PROPERTY_VISIBLE);
     }

     /* package */ void fireZoomChange() {
         fireInternalPropertyChange(INTERNAL_PROPERTY_ZOOMED);
     }
     
     public boolean getVisible() {
         if (isDisposed()) {
             return false;
         }
         return getPane().getVisible();
     }
     
     public void setVisible(boolean isVisible) {
         if (isDisposed()) {
             return;
         }
         getPane().setVisible(isVisible);
     }
     
     protected void firePropertyChange(int id) {

         if (queueEvents) {
             queuedEvents.set(id);
             return;
         }
         
         immediateFirePropertyChange(id);
     }
     
     private void immediateFirePropertyChange(int id) {
         UIListenerLogging.logPartReferencePropertyChange(this, id);
         Object listeners[] = propChangeListeners.getListeners();
         for (int i = 0; i < listeners.length; i++) {
             ((IPropertyListener) listeners[i]).propertyChanged(part, id);
         }
         
         fireInternalPropertyChange(id);
     }

     public final IWorkbenchPart getPart(boolean restore) {
         if (isDisposed()) {
             return null;
         }
         
         if (part == null && restore) {
             
             if (state == STATE_CREATION_IN_PROGRESS) {
                 IStatus result = WorkbenchPlugin.getStatus(
                         new PartInitException(NLS.bind("Warning: Detected recursive attempt by part {0} to create itself (this is probably, but not necessarily, a bug)", //$NON-NLS-1$
 getId())));
                 WorkbenchPlugin.log(result);
                 return null;
             }
             
             try {
                 state = STATE_CREATION_IN_PROGRESS;
                 
                 IWorkbenchPart newPart = createPart();
                 if (newPart != null) {
                     part = newPart;
                     // Add a dispose listener to the part. This dispose listener does nothing but log an exception
 // if the part's widgets get disposed unexpectedly. The workbench part reference is the only
 // object that should dispose this control, and it will remove the listener before it does so.
 getPane().getControl().addDisposeListener(prematureDisposeListener);
                     part.addPropertyListener(propertyChangeListener);
                     if (part instanceof IWorkbenchPart3) {
                         ((IWorkbenchPart3)part).addPartPropertyListener(partPropertyChangeListener);
                     }

                     refreshFromPart();
                     releaseReferences();
                     
                     fireInternalPropertyChange(INTERNAL_PROPERTY_OPENED);
                 }
             } finally {
                 state = STATE_CREATED;
             }
         }
         
         return part;
     }
     
     protected abstract IWorkbenchPart createPart();
         
     protected abstract PartPane createPane();
     
     /**
      * Returns the part pane for this part reference. Does not return null. Should not be called
      * if the reference has been disposed.
      *
      * TODO: clean up all code that has any possibility of calling this on a disposed reference
      * and make this method throw an exception if anyone else attempts to do so.
      *
      * @return
      */
     public final PartPane getPane() {
         
         // Note: we should never call this if the reference has already been disposed, since it
 // may cause a PartPane to be created and leaked.
 if (pane == null) {
             pane = createPane();
         }
         return pane;
     }

     public final void dispose() {
         
         if (isDisposed()) {
             return;
         }
         
         // Store the current title, tooltip, etc. so that anyone that they can be returned to
 // anyone that held on to the disposed reference.
 partName = getPartName();
         contentDescription = getContentDescription();
         tooltip = getTitleToolTip();
         title = getTitle();
         
         if (state == STATE_CREATION_IN_PROGRESS) {
             IStatus result = WorkbenchPlugin.getStatus(
                     new PartInitException(NLS.bind("Warning: Blocked recursive attempt by part {0} to dispose itself during creation", //$NON-NLS-1$
 getId())));
             WorkbenchPlugin.log(result);
             return;
         }
         
         // Disposing the pane disposes the part's widgets. The part's widgets need to be disposed before the part itself.
 if (pane != null) {
             // Remove the dispose listener since this is the correct place for the widgets to get disposed
 Control targetControl = getPane().getControl();
             if (targetControl != null) {
                 targetControl.removeDisposeListener(prematureDisposeListener);
             }
             pane.dispose();
         }
         
         doDisposePart();
    
         if (pane != null) {
             pane.removeContributions();
         }
         
         clearListenerList(internalPropChangeListeners);
         clearListenerList(partChangeListeners);
         Image oldImage = image;
         ImageDescriptor oldDescriptor = imageDescriptor;
         image = null;
         
         state = STATE_DISPOSED;
         imageDescriptor = ImageDescriptor.getMissingImageDescriptor();
         defaultImageDescriptor = ImageDescriptor.getMissingImageDescriptor();
         immediateFirePropertyChange(IWorkbenchPartConstants.PROP_TITLE);
         clearListenerList(propChangeListeners);
         
         if (oldImage != null) {
             JFaceResources.getResources().destroy(oldDescriptor);
         }
     }

     /**
      * Clears all of the listeners in a listener list. TODO Bug 117519 Remove
      * this method when fixed.
      *
      * @param list
      * The list to be clear; must not be <code>null</code>.
      */
     private final void clearListenerList(final ListenerList list) {
         final Object [] listeners = list.getListeners();
         for (int i = 0; i < listeners.length; i++) {
             list.remove(listeners[i]);
         }
     }

     /**
      *
      */
     protected void doDisposePart() {
         if (part != null) {
             fireInternalPropertyChange(INTERNAL_PROPERTY_CLOSED);
             // Don't let exceptions in client code bring us down. Log them and continue.
 try {
                 part.removePropertyListener(propertyChangeListener);
                 if (part instanceof IWorkbenchPart3) {
                     ((IWorkbenchPart3)part).removePartPropertyListener(partPropertyChangeListener);
                 }
                 part.dispose();
             } catch (Exception e) {
                 WorkbenchPlugin.log(e);
             }
             part = null;
         }
     }

     public void setPinned(boolean newPinned) {
         if (isDisposed()) {
             return;
         }

         if (newPinned == pinned) {
             return;
         }
         
         pinned = newPinned;
         
         setImageDescriptor(computeImageDescriptor());
         
         fireInternalPropertyChange(INTERNAL_PROPERTY_PINNED);
     }
     
     public boolean isPinned() {
         return pinned;
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.IWorkbenchPartReference#getPartProperty(java.lang.String)
      */
     public String getPartProperty(String key) {
         if (part != null) {
             if (part instanceof IWorkbenchPart3) {
                 return ((IWorkbenchPart3) part).getPartProperty(key);
             }
         } else {
             return (String )propertyCache.get(key);
         }
         return null;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.ui.IWorkbenchPartReference#addPartPropertyListener(org.eclipse.jface.util.IPropertyChangeListener)
      */
     public void addPartPropertyListener(IPropertyChangeListener listener) {
         if (isDisposed()) {
             return;
         }
         partChangeListeners.add(listener);
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.ui.IWorkbenchPartReference#removePartPropertyListener(org.eclipse.jface.util.IPropertyChangeListener)
      */
     public void removePartPropertyListener(IPropertyChangeListener listener) {
         if (isDisposed()) {
             return;
         }
         partChangeListeners.remove(listener);
     }
     
     protected void firePartPropertyChange(PropertyChangeEvent event) {
         Object [] l = partChangeListeners.getListeners();
         for (int i = 0; i < l.length; i++) {
             ((IPropertyChangeListener) l[i]).propertyChange(event);
         }
     }
     
     protected void createPartProperties(IWorkbenchPart3 workbenchPart) {
         Iterator i = propertyCache.entrySet().iterator();
         while (i.hasNext()) {
             Map.Entry e = (Map.Entry ) i.next();
             workbenchPart.setPartProperty((String ) e.getKey(), (String ) e.getValue());
         }
     }
 }

